package view.overlay;

import influence_radii.Radial;
import item.Item;
import item.Trap;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import map.Decal;
import map.GameMap;
import map.GameWorld;
import map.Position;
import map.terrain.Terrain;
import model.ModelObject;
import model.Observer;
import model.Subject;
import stat.StatType;
import view.drawing.DrawState;
import view.drawing.EntityDrawState;
import view.drawing.StaticDrawState;
import effects.CombatEffect;
import entity.Avatar;
import entity.Entity;

public class MapOverlay extends ActiveOverlay implements Observer {

	private Dimension preferredSize;
	
	private GameWorld world;
	private Avatar avatar;
	private Position center;
	private int avatarX;
	private int avatarY;
	private int centerX;
	private int centerY;
	private int offsetX;
	private int offsetY;
	
	private DrawState<Entity> mAvatarState;
	
	private Map<ModelObject, DrawState<?>> mStates;
	private Map<Position, Entity> mFoggedEntities;
	
	private Map<Position, ViewState> mViewStates;
	
	private GameMap mCurrentMap;
	
	enum ViewState {
		INVIEW,
		FOG,
		SHROUD
	}
	
	public MapOverlay() {
		
		mStates = new HashMap<ModelObject, DrawState<?>>();
		
		preferredSize = new Dimension(0, 0);
		
		world = GameWorld.getInstance();
		
		mAvatarState = new EntityDrawState(world.getAvatar());
		mFoggedEntities = new HashMap<Position, Entity>();
		
		mViewStates = new HashMap<Position, ViewState>();
		
		// observe the GameWorld
		GameWorld.getInstance().register(this);
		
		
	}
	
	private void changeMap(GameMap map) {
		mCurrentMap = map;
		
		// we need to update the states for this map
		mViewStates = new HashMap<Position, ViewState>();
		
		for (int x = 0; x < map.getWidth(); x++) {
			for (int y = 0; y < map.getHeight(); y++) {
				mViewStates.put(new Position(x, y), ViewState.SHROUD);
			}
		}
		
		// update the avatar viewing
		updateFogOfWar();
	}
	
	
	
	protected void paint(Graphics g) {
		

		GameMap map = mCurrentMap;
		
		
		mCurrentMap = map;
		
		if (avatar == null) {
			avatar = GameWorld.getInstance().getAvatar();
			avatar.register(this);
			avatar.getStats().register(this);
			updateFogOfWar();
		}
		
		
		// Update the model states
		mAvatarState.update(0);
		
		for (ModelObject o : mStates.keySet()) {
			DrawState<?> state = mStates.get(o);
			
			if (state != null) {
				state.update(0);
			}
		}
		
		if(center == null) {
			center = avatar.getPosition();
		}
		
		int overlayWidth = getWidth();
		int overlayHeight = getHeight();
		
		//fill the background with a solid color
		g.setColor(Color.BLACK);
		g.fillRect(0, 0, overlayWidth, overlayHeight);
		
		avatarX = avatar.getPosition().getX();
		avatarY = avatar.getPosition().getY();
		
		centerX = center.getX();
		centerY = center.getY();
		

		

		offsetX = overlayWidth / 2 - hexToPixel(centerX) - 50;
		offsetY = overlayHeight / 2 - hexToPixel(centerY) - 50;
		
		//calculate the first and last hex to display
		//in both the x and y coordinates
		int firstHexX = pixelToHex(-offsetX) - 1;
		int lastHexX = firstHexX + 2 * (centerX - firstHexX);
		int firstHexY = pixelToHex(-offsetY) - ((lastHexX - centerX));
		int lastHexY = centerY + (centerX - firstHexX);
		
		//iterate through the visible hexes and display
		//anything at their location
		for(int y = firstHexY; y <= lastHexY; y++) {
			for(int x = firstHexX; x <= lastHexX; x++) {
				if(x >= 0 && x < world.getWidth() && y >= 0 && y < world.getHeight()) {
					
					Position pos = new Position(x, y);
					
					ViewState vState = mViewStates.get(pos);
					
//					if(visiblePositions.contains(new Position(x, y))) {
					if (vState == ViewState.INVIEW) {
						
						((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
					}
					else if (vState == ViewState.FOG){
						
						((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f));
					}
					
					Point drawPosition = getDrawPosition(x, y);
					
					
					// Dont' draw anything for shroud
					if (vState == ViewState.SHROUD) {
//						System.out.format("Tile (%d, %d) is shrouded, not drawing.\n", pos.getX(), pos.getY());
						continue;
					}

					//draw terrain
					if(map.hasTerrain(pos)) {
						Terrain t = map.getTerrain(pos);
						// Check for an existing state
						DrawState<?> state;
						if (mStates.containsKey(t)) {
							state = mStates.get(t);
						}
						else {
							state = new StaticDrawState<Terrain>(t);
							mStates.put(t, state);
						}
						
						state.draw(g, drawPosition);
					}
					
					//draw decals
					if(map.hasDecal(pos)) {
						Decal d = map.getDecal(pos);
						// Check for an existing state
						DrawState<?> state;
						if (mStates.containsKey(d.getName())) {
							state = mStates.get(d.getName());
						}
						else {
							state = new StaticDrawState<Decal>(d);
							mStates.put(d, state);
						}
						
						state.draw(g, drawPosition);
					}
					
					//draw items
					if(map.hasItem(pos)) {
						Item i = map.getItem(pos);
						// Check for an existing state
						DrawState<?> state;
						if (mStates.containsKey(i.getName())) {
							state = mStates.get(i.getName());
						}
						else {
							state = new StaticDrawState<Item>(i);
							mStates.put(i, state);
						}
						
						state.draw(g, drawPosition);
					}
					
					//draw traps
					if(map.hasTrap(pos)) {
						Trap t = map.getTrap(pos);
						if(t.isVisible()) {
							// Check for an existing state
							DrawState<?> state;
							if (mStates.containsKey(t.getName())) {
								state = mStates.get(t.getName());
							}
							else {
								state = new StaticDrawState<Trap>(t);
								mStates.put(t, state);
							}
							
							state.draw(g, drawPosition);
						}
					}
					
					if (vState == ViewState.INVIEW) {
						//draw combat effects
						if(map.hasCombatEffect(pos)) {
							CombatEffect ce = map.getCombatEffect(pos);
							// Check for an existing state
							DrawState<?> state;
							if (mStates.containsKey(ce.getName())) {
								state = mStates.get(ce.getName());
							}
							else {
								state = new StaticDrawState<CombatEffect>(ce);
								mStates.put(ce, state);
							}
							
							state.draw(g, drawPosition);
						}
					}
					
					if (vState == ViewState.INVIEW) {
						//draw npcs
						if(map.hasEntity(pos)) {
							Entity e = map.getEntity(pos);
							
								// Check for an existing state
								DrawState<?> state;
								if (mStates.containsKey(e)) {
									state = mStates.get(e);
								}
								else {
									state = new EntityDrawState(e);
									mStates.put(e, state);
								}
								
								state.draw(g, drawPosition);
								
								if (! e.equals(avatar)) {
									int entityHealth = e.getStats().getStat(StatType.HP);
									int entityMaxHealth = e.getStats().getStat(StatType.MAXHP);
									
									int barWidth = 80;
									int barHeight = 10;
									Rectangle hpRect = new Rectangle(drawPosition.x,
																	drawPosition.y - barHeight,
																	barWidth,
																	barHeight);
									g.setColor(Color.RED);
									g.drawRect(hpRect.x, hpRect.y, hpRect.width, hpRect.height);
									g.fillRect(hpRect.x, hpRect.y, (int)(((double)entityHealth)/(double)entityMaxHealth) * hpRect.width, hpRect.height);
	
							
							}
							
						}
					}
					
					synchronized (mFoggedEntities) {
						if (mFoggedEntities.containsKey(pos)) {
							// draw it anyway
							DrawState<?> state = new EntityDrawState(mFoggedEntities.get(pos));
							state.draw(g, drawPosition);
						}
					}
				}
			}
		}
		
		((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
		// Draw the avatar
		Point drawPosition = getDrawPosition(avatarX, avatarY);
		mAvatarState.draw(g, drawPosition);
		
	}
	
	public Dimension getPreferredSize() {
		
		return preferredSize;
	}
	

	
	private Point getDrawPosition(int x, int y) {
		
		if(x < centerX) {
			
			return new Point((hexToPixel(x) + offsetX) + (25 * (centerX - x)), (hexToPixel(y) + offsetY) - (50 * (centerX - x)));
		}
		else if(x > centerX) {
			return new Point((hexToPixel(x) + offsetX) - (25 * (x - centerX)), (hexToPixel(y) + offsetY) + (50 * (x - centerX)));
		}
		else {
			
			return new Point(hexToPixel(x) + offsetX, hexToPixel(y) + offsetY);
		}
	}
	
	public Position getCenter() {
		
		return this.center;
	}
	
	public void setCenter(Position center) {
		
		this.center = center;
	}

	private int hexToPixel(int hex) {
		
		return hex * 100;
	}
	
	private int pixelToHex(int pixel) {
		
		return pixel / 100;
	}

	private void updateFogOfWar() {
		
		GameMap map = GameWorld.getInstance().getCurrentMap();
		
		if (avatar == null) return;
		
		//calculate the avatar's visible positions
		int range = avatar.getStats().getStat(StatType.VISIBILITY);
		Radial radial = new Radial();
		List<Position> visiblePositions = new ArrayList<Position>();
		for(int index = 0; index <= range; index++) {
			
			List<Position> tempPositions = radial.getRelAppsPts(index);
			
			for(Position pos : tempPositions) {
				visiblePositions.add(pos);
				
				Position toUpdate = avatar.getPosition().substract(pos);

				if (mViewStates.containsKey(toUpdate)) {
					ViewState curState = mViewStates.get(toUpdate);
					
					if (curState != ViewState.INVIEW){ 
						// change it to visible
						mViewStates.put(toUpdate, ViewState.INVIEW);
						
						
						synchronized (mFoggedEntities) {
							// now check if there are entities in this space that are not there anymore
							if (mFoggedEntities.containsKey(toUpdate)) {
								
								mFoggedEntities.remove(toUpdate);
							}
						}
					}

				}
				else {
					mViewStates.put(toUpdate, ViewState.INVIEW);
				}
			}
		}
		
		/*
		 * Make sure the tiles are visible
		 */
		List<Position> visibleToAvatar = new ArrayList<Position>();
		for (Position pos : visiblePositions) {
			visibleToAvatar.add(avatar.getPosition().substract(pos));
		}
		
		/*
		 * Check when we are going from INVIEW to FOG
		 */
		for (Position pos : mViewStates.keySet()) {
			ViewState state = mViewStates.get(pos);
			
			if (state == ViewState.INVIEW) {
				// if it is viewable
				if (! visibleToAvatar.contains(pos)) {
					// Change to fog and save what was there
					mViewStates.put(pos, ViewState.FOG);
					
					// If there was an entity there at the time
					Entity e = map.getEntity(pos);
					
					if (e != null) {
						// save this entity
						synchronized (mFoggedEntities) {
							mFoggedEntities.put(pos, e);
							e.register(this);
						}
					}
				}
			}
		}
	}
	
	

	private void checkEntitiesForGhosting(Subject source) {
		
		synchronized (mFoggedEntities) {
			
			
			List<Position> toRemove = new ArrayList<Position>();
			
			// Look for ghosts that shouldn't be there
			for (Position pos : mFoggedEntities.keySet()) {
				
				Entity e = mFoggedEntities.get(pos);
				
				if (! e.equals(source)) {
					continue;
				}
			
				
				// triggering entity
				//calculate the avatar's visible positions
				int range = avatar.getStats().getStat(StatType.VISIBILITY);
				Radial radial = new Radial();
				for(int index = 0; index <= range; index++) {
					
					List<Position> tempPositions = radial.getRelAppsPts(index);
					
					for (Position pos1 : tempPositions) {
						
						Position realPos = avatar.getPosition().substract(pos1);
						
						if (e.getPosition().equals(realPos)) {
							// entity is within this visible location
							for (Position p2 : mFoggedEntities.keySet()) {
								
								if (mFoggedEntities.get(p2).equals(e)) {
									toRemove.add(p2);
								}
							}
							e.unregister(this);
						}
					}
					
			
				}
				
			}
			
			
			// Do the removal
			for (Position p : toRemove) {
				mFoggedEntities.remove(p);
			}
			
		}

	}

	@Override
	public void update(Subject sourceSubject) {
		if (avatar != null) {
			if (sourceSubject.equals(avatar) || sourceSubject.equals(avatar.getStats())) {
				// source was avatar, do shroud checking
				updateFogOfWar();
			}
		}
		
		
		if (sourceSubject.equals(GameWorld.getInstance())) {
			// try to get the avatar again, can't hurt?
			avatar = GameWorld.getInstance().getAvatar();
			// map probably changed
			GameMap map = GameWorld.getInstance().getCurrentMap();
			if (map != null) {
				if (!map.equals(mCurrentMap)) {
					changeMap(map);
				}
			}
		}
		else {
			// check if some of the entities have moved into the avatar's sight
			checkEntitiesForGhosting(sourceSubject);
		}
		
	}
}
